/*
 * Copyright 2010 Gaurav Saxena
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.gwtstructs.gwt.client.widgets.autocompleterTextbox;

import java.util.List;

import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.HasAllFocusHandlers;
import com.google.gwt.event.dom.client.HasAllKeyHandlers;
import com.google.gwt.event.dom.client.HasAllMouseHandlers;
import com.google.gwt.event.dom.client.HasChangeHandlers;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.event.dom.client.MouseWheelHandler;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.i18n.client.BidiUtils;
import com.google.gwt.i18n.client.HasDirection.Direction;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasName;
import com.google.gwt.user.client.ui.HasText;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.TextBoxBase.TextAlignConstant;
import com.google.gwt.user.client.ui.ValueBoxBase.TextAlignment;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * Autocompleter Widget wraps a textbox with simultaneous filtering of possible values provided as a List. 
 * @author Gaurav Saxena
 */
public class AutoCompleterTextBox extends Composite implements HasChangeHandlers, HasText, HasName
, HasValue<String>,HasClickHandlers, HasAllFocusHandlers, HasAllKeyHandlers, HasAllMouseHandlers
{
	private VerticalPanel parentPanel = new VerticalPanel();
	private TextBox textBox;
	private ScrollPanel suggestionsPanel = new ScrollPanel();
	private VerticalPanel suggestionHolder = new VerticalPanel();
	private int currentHighLightedOption = -1;
	private boolean isComparisonCaseSensitive = false;
	private boolean isComparisonStartsFromBeginning = false;
	private List<String> suggestions;
	public enum PanelPosition {TOP, BOTTOM, LEFT, RIGHT};
	private PanelPosition position;
	private HandlerRegistration mouseDownHandler;
	private boolean showSuggestionsWhenTextBoxEmpty = true;
	private HandlerRegistration textboxKeyDownHandler;
	private HandlerRegistration textboxKeyUpHandler;
	private Command keyUpCallback;
	private Widget textboxContainer;
	private boolean renewWidth = true;

	/**
	 * @param suggestions List of String from which filtering of strings would be done
	 * @param isComparisonCaseSensitive if the String comparison needs to be case sensitive. Default: false
	 * @param isComparisonStartsFromBeginning if the comparison should begin from the beginning of the string. 
	 * Default: false
	 * @param position Position of Suggestion Panel among left, top, right, bottom
	 */
	public AutoCompleterTextBox(List<String> suggestions, boolean isComparisonCaseSensitive
			, boolean isComparisonStartsFromBeginning, PanelPosition position)
	{
		this(suggestions, isComparisonCaseSensitive, position);
		this.isComparisonStartsFromBeginning  = isComparisonStartsFromBeginning;
	}
	/**
	 * @param suggestions List of String from which filtering of strings would be done
	 * @param isComparisonCaseSensitive if the String comparison needs to be case sensitive
	 * @param position Position of Suggestion Panel among left, top, right, bottom
	 */
	public AutoCompleterTextBox(List<String> suggestions, boolean isComparisonCaseSensitive
			, PanelPosition position)
	{
		this(suggestions, position);
		this.isComparisonCaseSensitive = isComparisonCaseSensitive;
	}
	protected AutoCompleterTextBox(TextBox textBox, final List<String> suggestions, PanelPosition position){
		this(textBox, textBox, suggestions, position);
	}
	protected AutoCompleterTextBox(final TextBox textBox, Widget textboxContainer, final List<String> suggestions
			, PanelPosition position){
		initWidget(parentPanel);
		this.textBox = textBox;
		this.textboxContainer = textboxContainer;
		this.textboxContainer.setWidth("100%");
		this.textBox.setWidth("100%");
		this.textboxContainer.setHeight("100%");
		this.textBox.setHeight("100%");
		this.position = position;
		suggestionsPanel.setVisible(false);
		suggestionsPanel.setStyleName("autocompleter-suggestionPanel");
		DOM.setStyleAttribute(suggestionsPanel.getElement(), "position", "absolute");
		suggestionsPanel.add(suggestionHolder);
		DOM.setStyleAttribute(textBox.getElement(), "position", "relative");
		DOM.setStyleAttribute(suggestionsPanel.getElement(), "zIndex", Integer.MAX_VALUE + "");
		suggestionsPanel.setPixelSize(textBox.getOffsetWidth(), 200);
		switch(position)
		{
			case TOP:
				RootPanel.get().add(suggestionsPanel);
				parentPanel.add(textboxContainer);
				break;
			case BOTTOM:
				parentPanel.add(textboxContainer);
				RootPanel.get().add(suggestionsPanel);
				break;
			case LEFT:
			{
				parentPanel.add(textboxContainer);
				RootPanel.get().add(suggestionsPanel);
			}
				break;
			case RIGHT:
			{
				RootPanel.get().add(suggestionsPanel);
				parentPanel.add(textboxContainer);
			}
				break;
		}
		suggestionHolder.setWidth("100%");
		this.suggestions = suggestions;
		this.textboxKeyUpHandler = addKeyUpHandler();
		textBox.addKeyUpHandler(new KeyUpHandler(){
			@Override
			public void onKeyUp(KeyUpEvent event) {
				DOM.releaseCapture(textBox.getElement());
				if(!KeyUpEvent.isArrow(event.getNativeKeyCode()) 
					&& event.getNativeKeyCode() != KeyCodes.KEY_ENTER
					&& event.getNativeKeyCode() != KeyCodes.KEY_ESCAPE)
				{
					if(keyUpCallback != null)
						keyUpCallback.execute();
					prepareSuggestions();
				}
			}
		});
		this.textboxKeyDownHandler = addKeyDownHandler();
		mouseDownHandler = addMouseHandlerToTextBox();
		
		textBox.addBlurHandler(new BlurHandler(){
			@Override
			public void onBlur(BlurEvent event) {
					Timer t = new Timer(){
					@Override
					public void run() {
						if(isValidBlur())
							suggestionsPanel.setVisible(false);
					}};
					t.schedule(100);
		}});
		DOM.setStyleAttribute(this.getElement(), "zIndex", Integer.toString(Integer.MAX_VALUE));
		setUpSuggestionPanelEvents(suggestionsPanel.getElement(), textBox.getElement());
	}
	/**
	 * By Default the string comparison is case insensitive.
	 * @param suggestions List of String from which filtering of strings would be done
	 * @param position Position of Suggestion Panel among left, top, right, bottom
	 */
	public AutoCompleterTextBox(final List<String> suggestions, PanelPosition position)
	{
		this(new TextBox(), suggestions, position);
	}
	/**
	 * @param suggestions List of String from which filtering of strings would be done
	 */
	public AutoCompleterTextBox(final List<String> suggestions)
	{
		this(suggestions, PanelPosition.BOTTOM);
	}
	private HandlerRegistration addMouseHandlerToTextBox() {
		return textBox.addMouseDownHandler(new MouseDownHandler(){
			@Override
			public void onMouseDown(MouseDownEvent event) {
				if(!suggestionsPanel.isVisible())
					prepareSuggestions();
		}});
	}
	private native boolean isValidBlur() /*-{
		return window.isValidBlur;
	}-*/;
	private native void setUpSuggestionPanelEvents(Element suggestionPanel, Element textBox) /*-{
		window.isValidBlur = true;
    	suggestionPanel.onmousedown = function(){
    		window.isValidBlur = false;
    	};
    	suggestionPanel.onmouseout = function(){
    		textBox.focus();
    		window.isValidBlur = true;
    	};
  	}-*/;
	
	private HandlerRegistration addKeyDownHandler() {
		return textBox.addKeyDownHandler(new KeyDownHandler(){
			@Override
			public void onKeyDown(KeyDownEvent event) {
				if(event.isUpArrow())
				{
					DOM.setCapture(getTextBox().getElement());
					if(!suggestionsPanel.isVisible())
						showSuggestionsPanel();
					else if(currentHighLightedOption > 0)
					{
						unHighlight(currentHighLightedOption);
						hightlight(--currentHighLightedOption);
					}
					else
					{
						hightlight(0);
						currentHighLightedOption = 0;
					}
				}
				else if(event.isDownArrow())
				{
					DOM.setCapture(getTextBox().getElement());
					if(!suggestionsPanel.isVisible())
						showSuggestionsPanel();
					else if(currentHighLightedOption < suggestionHolder.getWidgetCount() - 1)
					{
						unHighlight(currentHighLightedOption);
						hightlight(++currentHighLightedOption);
					}
					else
					{
						hightlight(suggestionHolder.getWidgetCount() - 1);
						currentHighLightedOption = suggestionHolder.getWidgetCount() - 1;
					}
				}
			}
			private void showSuggestionsPanel() {
				unHighlight(currentHighLightedOption);
				if(!suggestionsPanel.isVisible())
				{
					currentHighLightedOption = -1;
					prepareSuggestions();
				}
			}
		});
	}
	private HandlerRegistration addKeyUpHandler() {
		return textBox.addKeyUpHandler(new KeyUpHandler(){
			@Override
			public void onKeyUp(KeyUpEvent event) 
			{
				if(event.getNativeKeyCode() == KeyCodes.KEY_ENTER)
					setSugestion(suggestions, Integer.valueOf(
						DOM.getElementProperty(suggestionHolder.getWidget(currentHighLightedOption).getElement()
						, "index")));
				else if(event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE)
				{
					suggestionsPanel.setVisible(false);
					//event.stopPropagation();
				}
			}
		});
	}

	private void setSugestion(List<String> suggestions, int suggestionIndex) {
		setTextBoxValue(suggestions, suggestionIndex);
		currentHighLightedOption = 0;
		suggestionsPanel.setVisible(false);
		if(getTextBox().getText().length() >= getText().length())//GraphicEnabledAutocompleterTextBox creates String for getText and actually has no value in textbox 
			getTextBox().setCursorPos(getText().length());
	}
	protected void setTextBoxValue(List<String> suggestions, int suggestionIndex) {
		setText(suggestions.get(suggestionIndex));
	}
	protected void prepareSuggestions() {
		if(!showSuggestionsWhenTextBoxEmpty && getTextBox().getValue().length() == 0)
		{
			suggestionsPanel.setVisible(false);
			return;
		}
		String currentText = getCurrentText();
		int suggestionsLength = suggestionHolder.getWidgetCount();
		suggestionHolder.clear();
		boolean isMatchFound = false;
		for(int i = 0; i < suggestions.size(); i++)
		{
			int index;
			if(isComparisonCaseSensitive)
				index = getSuggestionFilterKey(i).indexOf(currentText);
			else
				index = getSuggestionFilterKey(i).toLowerCase().indexOf(currentText.toLowerCase());
			if((!isComparisonStartsFromBeginning && index > -1) ||
					(isComparisonStartsFromBeginning && index == 0))
			{
				isMatchFound = true;
				suggestionHolder.add(getSuggestion(suggestions, i, index, currentText));
			}
		}
		if(isMatchFound)
		{
			suggestionsPanel.setVisible(true);
			suggestionsPanel.setHeight(Math.min(200, suggestionHolder.getOffsetHeight()) + "px");
			if(renewWidth)
			{
				suggestionsPanel.setWidth(Math.max(getTextBox().getOffsetWidth(), suggestionsPanel.getOffsetWidth()) + "px");
				renewWidth = false;
			}
			/*
			 * in FF - The first time suggestionsPanel is rendered, suggestionHolder's height is calculated 
			 * incorrectly. After setting the height of suggestionPanel, suggestionHolder height is calculated 
			 * correctly
			 */
			if(position == PanelPosition.TOP) {
				DOM.setStyleAttribute(suggestionsPanel.getElement(), "top"
						, (DOM.getAbsoluteTop(getTextBox().getElement()) - suggestionsPanel.getOffsetHeight()) + "px");
				DOM.setStyleAttribute(suggestionsPanel.getElement(), "left"
						, DOM.getAbsoluteLeft(getTextBox().getElement()) + "px");
			} else if(position == PanelPosition.LEFT) {
				DOM.setStyleAttribute(suggestionsPanel.getElement(), "top"
						, DOM.getAbsoluteTop(getTextBox().getElement()) + "px");
				DOM.setStyleAttribute(suggestionsPanel.getElement(), "left"
						, (DOM.getAbsoluteLeft(getTextBox().getElement()) - suggestionsPanel.getOffsetWidth()) + "px");
			} else if(position == PanelPosition.RIGHT) {
				DOM.setStyleAttribute(suggestionsPanel.getElement(), "top"
						, DOM.getAbsoluteTop(getTextBox().getElement()) + "px");
				DOM.setStyleAttribute(suggestionsPanel.getElement(), "left"
						, (DOM.getAbsoluteLeft(getTextBox().getElement()) + textBox.getElement().getOffsetWidth()) + "px");
			} else if(position == PanelPosition.BOTTOM) {
				DOM.setStyleAttribute(suggestionsPanel.getElement(), "top"
						, (DOM.getAbsoluteTop(getTextBox().getElement()) + textBox.getElement().getOffsetHeight()) + "px");
				DOM.setStyleAttribute(suggestionsPanel.getElement(), "left"
						, (DOM.getAbsoluteLeft(getTextBox().getElement())) + "px");
			}
			if(suggestionsLength != suggestionHolder.getWidgetCount())
				currentHighLightedOption = 0;
			hightlight(currentHighLightedOption);
		}
		else
			suggestionsPanel.setVisible(false);
		
	}
	private HTML getSuggestion(final List<String> suggestions, final int loopIndex, int index
			, String textBoxValue) {
		final HTML option = getSuggestionHtml(loopIndex, index, textBoxValue);
		option.addMouseOverHandler(new MouseOverHandler(){public void onMouseOver(MouseOverEvent event) {
			unHighlight(currentHighLightedOption);
			currentHighLightedOption = suggestionHolder.getWidgetIndex(option);
			hightlight(currentHighLightedOption);
		}});
		option.addClickHandler(new ClickHandler(){public void onClick(ClickEvent event) {
			setSugestion(suggestions, loopIndex);
		}});
		option.setStyleName("autocomplete-option");
		DOM.setElementProperty(option.getElement(), "index", Integer.toString(loopIndex));
		return option;
	}
	private void unHighlight(int currentHighLightedIndex) {
		if(currentHighLightedIndex > -1 && currentHighLightedIndex < suggestionHolder.getWidgetCount())
		{
			Widget currentHighLightedWidget = suggestionHolder.getWidget(currentHighLightedIndex);
			currentHighLightedWidget.removeStyleName("autocomplete-option-highlight");
			currentHighLightedWidget.addStyleName("autocomplete-option-unhighlight");
		}
	}

	private void hightlight(int currentHighLightedIndex) {
		Widget currentHighLightedWidget;
		if(currentHighLightedIndex < 0)
			currentHighLightedOption = 0;
		else if(currentHighLightedIndex >= suggestionHolder.getWidgetCount())
			currentHighLightedOption = suggestionHolder.getWidgetCount() - 1;
		currentHighLightedWidget = suggestionHolder.getWidget(currentHighLightedOption);
		currentHighLightedWidget.removeStyleName("autocomplete-option-unhighlight");
		currentHighLightedWidget.addStyleName("autocomplete-option-highlight");
		currentHighLightedWidget.getElement().scrollIntoView();
	}

	public int getMaxLength() {
	    return getTextBox().getMaxLength();
	  }

	  /**
	   * Gets the number of visible characters in the text box.
	   * 
	   * @return the number of visible characters
	   */
	  public int getVisibleLength() {
	    return getTextBox().getVisibleLength();
	  }

	  public void setDirection(Direction direction) {
	    BidiUtils.setDirectionOnElement(getElement(), direction);
	  }

	  /**
	   * Sets the maximum allowable length of the text box.
	   * 
	   * @param length the maximum length, in characters
	   */
	  public void setMaxLength(int length) {
	    getTextBox().setMaxLength(length);
	  }

	  /**
	   * Sets the number of visible characters in the text box.
	   * 
	   * @param length the number of visible characters
	   */
	  public void setVisibleLength(int length) {
		  getTextBox().setVisibleLength(length);
	  }

	  public String getText() {
		  return getTextBox().getText();
	  }
	  public HandlerRegistration addChangeHandler(ChangeHandler handler) {
		  return getTextBox().addChangeHandler(handler);
	  }

	  public HandlerRegistration addValueChangeHandler(ValueChangeHandler<String> handler) 
	  {
		  return getTextBox().addValueChangeHandler(handler);
	  }

	  /**
	   * If a keyboard event is currently being handled on this text box, calling
	   * this method will suppress it. This allows listeners to easily filter
	   * keyboard input.
	   */
	  public void cancelKey() {
	    getTextBox().cancelKey();
	  }

	  /**
	   * Gets the current position of the cursor (this also serves as the beginning
	   * of the text selection).
	   * 
	   * @return the cursor's position
	   */
	  public int getCursorPos() {
	    return getTextBox().getCursorPos();
	  }

	  public String getName() {
	    return getTextBox().getName();
	  }

	  /**
	   * Gets the text currently selected within this text box.
	   * 
	   * @return the selected text, or an empty string if none is selected
	   */
	  public String getSelectedText() {
	    return getTextBox().getSelectedText();
	  }

	  /**
	   * Gets the length of the current text selection.
	   * 
	   * @return the text selection length
	   */
	  public int getSelectionLength() {
	    return getTextBox().getSelectionLength();
	  }

	  public String getValue() {
	    return getTextBox().getText();
	  }

	  /**
	   * Determines whether or not the widget is read-only.
	   * 
	   * @return <code>true</code> if the widget is currently read-only,
	   *         <code>false</code> if the widget is currently editable
	   */
	  public boolean isReadOnly() {
	    return getTextBox().isReadOnly();
	  }

	  @Override
	  public void onBrowserEvent(Event event) {
	    getTextBox().onBrowserEvent(event);
	  }

	  /**
	   * Selects all of the text in the box.
	   * 
	   * This will only work when the widget is attached to the document and not
	   * hidden.
	   */
	  public void selectAll() {
	    getTextBox().selectAll();
	  }

	  /**
	   * Sets the cursor position.
	   * 
	   * This will only work when the widget is attached to the document and not
	   * hidden.
	   * 
	   * @param pos the new cursor position
	   */
	  public void setCursorPos(int pos) {
	    getTextBox().setCursorPos(pos);
	  }

	  /**
	   * If a keyboard event is currently being handled by the text box, this method
	   * replaces the unicode character or key code associated with it. This allows
	   * listeners to easily filter keyboard input.
	   * 
	   * @param key the new key value
	   * @deprecated this method only works in IE and should not have been added to
	   *             the API
	   */
	  @Deprecated
	  public void setKey(char key) {
	    getTextBox().setKey(key);
	  }

	  public void setName(String name) {
	    getTextBox().setName(name);
	  }

	  /**
	   * Turns read-only mode on or off.
	   * 
	   * @param readOnly if <code>true</code>, the widget becomes read-only; if
	   *          <code>false</code> the widget becomes editable
	   */
	  public void setReadOnly(boolean readOnly) {
	    getTextBox().setReadOnly(readOnly);
	  }

	  /**
	   * Sets the range of text to be selected.
	   * 
	   * This will only work when the widget is attached to the document and not
	   * hidden.
	   * 
	   * @param pos the position of the first character to be selected
	   * @param length the number of characters to be selected
	   */
	  public void setSelectionRange(int pos, int length) {
	    getTextBox().setSelectionRange(pos, length);
	  }

	  /**
	   * Sets this object's text.  Note that some browsers will manipulate the text
	   * before adding it to the widget.  For example, most browsers will strip all
	   * <code>\r</code> from the text, except IE which will add a <code>\r</code>
	   * before each <code>\n</code>.  Use {@link #getText()} to get the text
	   * directly from the widget.
	   * 
	   * @param text the object's new text
	   */
	  public void setText(String text) {
	    getTextBox().setText(text);
	  }

	  /**
	   * Deprecated because GWT deprecated {@link TextAlignConstant}. 
	   * Use {@link #setTextAlignment(TextAlignment)}
	   * 
	   * Sets the alignment of the text in the text box.
	   * 
	   * @param align the text alignment (as specified by {@link #ALIGN_CENTER},
	   *          {@link #ALIGN_JUSTIFY}, {@link #ALIGN_LEFT}, and
	   *          {@link #ALIGN_RIGHT})
	   */
	  @Deprecated
	  public void setTextAlignment(TextAlignConstant align) {
	    getTextBox().setTextAlignment(align);
	  }
	  /**
	   * Sets the alignment of the text in the text box.
	   * 
	   * @param align the text alignment (as specified by {@link TextAlignment#CENTER}
	   * , {@link TextAlignment#LEFT},{@link TextAlignment#RIGHT}, {@link TextAlignment#JUSTIFY})
	   */
	  public void setTextAlignment(TextAlignment align) {
	    getTextBox().setAlignment(align);
	  }

	  public void setValue(String value) {
	    getTextBox().setValue(value);
	  }

	  public void setValue(String value, boolean fireEvents) {
	    getTextBox().setValue(value, fireEvents);
	  }
	  public HandlerRegistration addBlurHandler(BlurHandler handler) {
	    return getTextBox().addBlurHandler(handler);
	  }

	  public HandlerRegistration addClickHandler(ClickHandler handler) {
	    return getTextBox().addClickHandler(handler);
	  }

	  public HandlerRegistration addFocusHandler(FocusHandler handler) {
	    return getTextBox().addFocusHandler(handler);
	  }

	/**
	 * UP and DOWN arrow keys are used internally to support suggestions. Exercise caution when adding
     * custom keyup handlers
	 */
	public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
	    return getTextBox().addKeyDownHandler(handler);
	  }

	  public HandlerRegistration addKeyPressHandler(KeyPressHandler handler) {
	    return getTextBox().addKeyPressHandler(handler);
	  }
	  
    /**
     * ESCAPE and ENTER keys are used internally to support suggestions. Exercise caution when adding
     * custom keyup handlers
	 */
	public HandlerRegistration addKeyUpHandler(KeyUpHandler handler) {
	    return getTextBox().addKeyUpHandler(handler);
	  }

	  public HandlerRegistration addMouseDownHandler(MouseDownHandler handler) {
	    return getTextBox().addMouseDownHandler(handler);
	  }

	  public HandlerRegistration addMouseMoveHandler(MouseMoveHandler handler) {
	    return getTextBox().addMouseMoveHandler(handler);
	  }

	  public HandlerRegistration addMouseOutHandler(MouseOutHandler handler) {
	    return getTextBox().addMouseOutHandler(handler);
	  }

	  public HandlerRegistration addMouseOverHandler(MouseOverHandler handler) {
	    return getTextBox().addMouseOverHandler(handler);
	  }

	  public HandlerRegistration addMouseUpHandler(MouseUpHandler handler) {
	    return getTextBox().addMouseUpHandler(handler);
	  }

	  public HandlerRegistration addMouseWheelHandler(MouseWheelHandler handler) {
	    return getTextBox().addMouseWheelHandler(handler);
	  }

	  /**
	   * Gets the tab index.
	   * 
	   * @return the tab index
	   */
	  public int getTabIndex() {
	    return getTextBox().getTabIndex();
	  }

	  /**
	   * Gets whether this widget is enabled.
	   * 
	   * @return <code>true</code> if the widget is enabled
	   */
	  public boolean isEnabled() {
	    return getTextBox().isEnabled();
	  }

	  public void setAccessKey(char key) {
	    getTextBox().setAccessKey(key);
	  }

	  /**
	   * Sets whether this widget is enabled.
	   * 
	   * @param enabled <code>true</code> to enable the widget, <code>false</code>
	   *          to disable it
	   */
	  public void setEnabled(boolean enabled) {
	    getTextBox().setEnabled(enabled);
	  }

	  public void setFocus(boolean focused) {
	   getTextBox().setFocus(focused);
	  }

	  public void setTabIndex(int index) {
	    getTextBox().setTabIndex(index);
	  }
	/**
	 * @return List of string suggestions
	 */
	@SuppressWarnings("rawtypes")
	public List getSuggestions() {
		return suggestions;
	}
	/**
	 * The change in the list takes effect immediately. The list of suggestions change as soon as the method is 
	 * invoked
	 * @param suggestions List of string suggestions
	 */
	@SuppressWarnings("unchecked")
	public void setSuggestions(@SuppressWarnings("rawtypes") List suggestions) {
		this.suggestions = suggestions;
		this.renewWidth = true;
		prepareSuggestions();
	}
	/**
	 * Sets whether suggestion panel will open when textbox is clicked. By default, showing suggestions when
	 * text box is clicked is enabled
	 * @param disabled if true, behavior is disabled; behavior is enabled if false 
	 */
	public void setOpenOnClickBehavior(boolean disabled)
	{
		if(disabled)
			mouseDownHandler.removeHandler();
		else
			mouseDownHandler = addMouseHandlerToTextBox();
	}
	/**
	 * Sets whether suggestions will be shown if textbox is empty. By default, showing suggestion when textbox is
	 * empty is enabled 
	 * @param disabled if true, suggestions will not be shown; if false, suggestion will be shown
	 */
	public void setShowSuggestionsWhenTextBoxEmpty(boolean disabled)
	{
		this.showSuggestionsWhenTextBoxEmpty = !disabled;
	}
	/**
	 * Sets whether keyboard handling will work for suggestions. By default, Keyboard handling is enabled
	 * @param disabled if true keyboard shortcuts like up arrow, down arrow, escape and enter will not work, if 
	 * false, these will work 
	 */
	public void setKeyboardHandlingBehavior(boolean disabled)
	{
		if(disabled)
		{
			textboxKeyDownHandler.removeHandler();
			textboxKeyUpHandler.removeHandler();
		}
		else
		{
			textboxKeyDownHandler = addKeyDownHandler();
			textboxKeyUpHandler = addKeyUpHandler();
		}
	}
	/**
	 * Sets the width of the suggestions panel. By default it is as long as the textbox
	 * @param width
	 */
	public void setSuggestionWidth(int width)
	{
		suggestionsPanel.setWidth(width + "px");
	}
	/**
	 * Adds the keyup call back to the textbox 
	 * @param cmd callback
	 */
	public void addKeyUpCallback(Command cmd)
	{
		this.keyUpCallback = cmd;
	}
	/**
	 * Removes the key up callback from the textbox
	 */
	public void removeKeyUpCallback()
	{
		this.keyUpCallback = null;
	}
	@Override
	public void setHeight(String height){
		super.setHeight(height);
		textBox.setHeight(height);
	}
	protected Widget getTextBoxContainer()
	{
		return textboxContainer;
	}
	protected TextBox getTextBox()
	{
		return textBox;
	}
	protected String getCurrentText() {
		return getText();
	}
	protected String getSuggestionFilterKey(int loopIndex) {
		return suggestions.get(loopIndex);
	}
	protected HTML getSuggestionHtml(int loopIndex, int position, String currentTextValue) {
		return new HTML(suggestions.get(loopIndex).substring(0, position) 
				+ "<b>" + suggestions.get(loopIndex).substring(position, position + currentTextValue.length()) 
				+ "</b>" + suggestions.get(loopIndex).substring(position + currentTextValue.length()
						, suggestions.get(loopIndex).length()));
	}
}